﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CommonTypes.Client;
using System.Collections;
using System.Threading;
using Client.Model;
using System.Reflection;
using System.Diagnostics;

namespace Client.Threads
{
    public enum ReservationInitiatorState { FINISHED, CANCELED, WAITING_RESERVATION_ANSWERS, WAITING_TB_ANSWERS, WAITING_CR_ANSWERS, WAITING_DC_ANSWERS }

    public class ReservationInitiatorThread
    {
        private ClientData _clientData;

        private Reservation _reservation;

        private ArrayList _lastingClients;

        private List<Request> _requests;

        private Object _lockingObject;

        private ReservationInitiatorState _reservationThreadState;

        private static readonly int _reservationAnswerTimeout = 10000;
        private static readonly int _numberOfTries = 3;
        private static readonly int _timeoutBetweenTries = _reservationAnswerTimeout * 2;

        private int _tries;

        private int _transactionTimeout;

        private int _tentativelySlotIndex;

        private Thread _reservationThread;

        private Stopwatch _reservationTimer;

        public ReservationInitiatorThread(ClientData clientData, Reservation reservation)
        {
            _clientData = clientData;
            _reservation = reservation;
            _lastingClients = (ArrayList)_reservation.InvitedClients.Clone();

            _requests = new List<Request>();
            _lockingObject = new Object();
            _reservationThreadState = ReservationInitiatorState.WAITING_RESERVATION_ANSWERS;
            _tentativelySlotIndex = 0;
            _tries = 0;
            _reservationTimer = new Stopwatch();
        }

        public Reservation Reservation
        {
            get { return _reservation; }
        }

        public Thread ReservationThread
        {
            set { _reservationThread = value; }
        }

        public void AddClientRequest(Request request)
        {
            lock (_lockingObject)
            {
                _requests.Add(request);
                Monitor.Pulse(_lockingObject);
            }
        }

        public void Kill()
        {
            _reservationThread.Abort();
        }

        private bool CanStart()
        {
            bool ret = false;
            foreach (ReservationSlot resSlot in _reservation.ReservationSlots)
            {
                bool reservationSucceeded = _clientData.TrySlotReservation(resSlot.SlotNumber, _reservation);
                if (reservationSucceeded)
                    ret = true;
            }
            return ret;
        }

        private void AbortCurrentSlotTransaction(bool disseminate)
        {
            if (disseminate)
                DisseminateTransactionAbort();

            ReservationSlot rs = (ReservationSlot)_reservation.ReservationSlots[_tentativelySlotIndex];
            _clientData.RemoveTbReservation(rs.SlotNumber, _reservation.TicketNumber);

            _tentativelySlotIndex++;
            if (_reservation.ReservationSlots.Count == _tentativelySlotIndex)
            {
                _reservationThreadState = ReservationInitiatorState.CANCELED;
                _reservation.ReservationState = ReservationState.CANCELED;
                _requests.Clear();
            }
        }

        private void PrepareAnotherTry()
        {
            foreach (ReservationSlot rs in _reservation.ReservationSlots)
            {
                _clientData.RemoveTbReservation(rs.SlotNumber, _reservation.TicketNumber);
            }
            _tentativelySlotIndex = 0;
            _tries++;
            _reservationThreadState = ReservationInitiatorState.WAITING_RESERVATION_ANSWERS;

            Console.WriteLine("Trying again the the reservation {0}", _reservation.Description);
        }

        public void Run()
        {
            _reservationTimer.Start();
            
            if (_reservationThreadState == ReservationInitiatorState.CANCELED || _reservationThreadState == ReservationInitiatorState.FINISHED)
                return;

            while (_tries < _numberOfTries && _reservationThreadState != ReservationInitiatorState.FINISHED)
            {
                if (!CanStart())
                {
                    System.Console.WriteLine("I don't have available slots to initiate the reservation with name {0}. Stopping initiaton...", _reservation.Description);
                    _reservationThreadState = ReservationInitiatorState.CANCELED;
                    return;
                }

                _lastingClients = (ArrayList)_reservation.InvitedClients.Clone();

                DisseminateBeginReservation();
                bool answer = WaitReservationAnswer();
                if (!answer)
                {
                    _reservationThreadState = ReservationInitiatorState.CANCELED;
                    System.Console.WriteLine("Someone has no available slots to initiate the reservation with name {0}. Stopping initiaton...", _reservation.Description);
                }

                while (_reservationThreadState != ReservationInitiatorState.CANCELED && _reservationThreadState != ReservationInitiatorState.FINISHED)
                {
                    ReservationSlot rs = (ReservationSlot)_reservation.ReservationSlots[_tentativelySlotIndex];

                    if (rs.SlotState != ReservationSlotState.ABORTED && _clientData.TryTentativelyBooked(rs.SlotNumber, _reservation.TicketNumber))
                    {
                        DisseminateTentativelyBooked();
                        bool tentativelyBooked = WaitTentativelyBookedAnswer();
                        if (!tentativelyBooked)
                        {
                            AbortCurrentSlotTransaction(true);
                            continue;
                        }
                    }
                    else
                    {
                        AbortCurrentSlotTransaction(false);
                        continue;
                    }

                    if (_clientData.ProcessVoteCommit(rs.SlotNumber, _reservation.TicketNumber)) //&& DecideToVoteCommit(rs.SlotNumber))
                    {
                        DisseminateVoteCommitRequest();
                        bool canCommit = WaitCommitRequestAnswer();
                        if (!canCommit)
                        {
                            AbortCurrentSlotTransaction(true);
                            continue;
                        }
                    }
                    else
                    {
                        AbortCurrentSlotTransaction(true);
                        continue;
                    }

                    if (_clientData.ProcessDoCommit(rs.SlotNumber, _reservation.TicketNumber))
                    {
                        DisseminateVoteDoCommitRequest();
                        bool commit = WaitDoCommitRequestAnswer();
                        if (!commit)
                        {
                            AbortCurrentSlotTransaction(true);
                            continue;
                        }
                        else
                        {
                            _reservationThreadState = ReservationInitiatorState.FINISHED;
                            _reservation.ReservationState = ReservationState.SUCCEEDED;
                            Console.WriteLine("--> My Reservation {0} with name \"{1}\" succeeded on slot {2} in {3} ms.", _reservation.TicketNumber, _reservation.Description, rs.SlotNumber, _reservationTimer.ElapsedMilliseconds);
                            return;
                        }
                    }
                    else
                    {
                        AbortCurrentSlotTransaction(true);
                        continue;
                    }
                }

                Console.WriteLine("--> My Reservation {0} with name \"{1}\" has failed.", _reservation.TicketNumber, _reservation.Description);
                PrepareAnotherTry();
                Thread.Sleep(_timeoutBetweenTries);
            }
        }

        private bool WaitReservationAnswer()
        {
            long lastTimestamp, begginingTimeStampMilis = (long)(DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond);
            int elapsedMilis = 0;

            //Wait for answers from dissemination
            while (_lastingClients.Count > 0 && _reservationThreadState == ReservationInitiatorState.WAITING_RESERVATION_ANSWERS
                && elapsedMilis < _reservationAnswerTimeout)
            {
                lock (_lockingObject)
                {
                    Monitor.Wait(_lockingObject, _reservationAnswerTimeout - elapsedMilis);

                    while (_requests.Count > 0)
                    {
                        Request request = _requests[0];

                        if (request.RequestProtocolPhase == RequestPhase.RESERVATION_ANSWER)
                        {
                            ArrayList availableSlots = request.AvailableSlots;
                            bool existAvailableSlots = _reservation.MergeClientSlots(availableSlots);

                            if (!existAvailableSlots)
                            {
                                _reservationThreadState = ReservationInitiatorState.CANCELED;
                                return false;
                            }
                            _lastingClients.Remove(request.ClientId);
                        }

                        _requests.Remove(request);
                    }
                }

                lastTimestamp = (long)(DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond);
                elapsedMilis = (int)(lastTimestamp - begginingTimeStampMilis);
            }

            //Answer from some clients not received. Contact directly.
            int retryElapsedMilis = 0;

            while (_lastingClients.Count > 0)
            {
                long retryBegginingTimestamp = (long)(DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond);
                retryElapsedMilis = 0;

                Type typExternal = typeof(CommonTypes.Client.IClientToClient);
                MethodInfo methodInf = typExternal.GetMethod("BeginReservation");

                object[] args = new object[] { _reservation, new ArrayList(), _lastingClients };
                MessageDissemination.disseminateMessage(methodInf, _clientData, _lastingClients, args, true);

                while (retryElapsedMilis < _reservationAnswerTimeout)
                {
                    lock (_lockingObject)
                    {
                        Monitor.Wait(_lockingObject, _reservationAnswerTimeout - retryElapsedMilis);

                        while (_requests.Count > 0)
                        {
                            Request request = _requests[0];

                            if (request.RequestProtocolPhase == RequestPhase.RESERVATION_ANSWER)
                            {
                                ArrayList availableSlots = request.AvailableSlots;
                                bool existAvailableSlots = _reservation.MergeClientSlots(availableSlots);

                                if (!existAvailableSlots)
                                {
                                    _reservationThreadState = ReservationInitiatorState.CANCELED;
                                    return false;
                                }
                                _lastingClients.Remove(request.ClientId);
                            }

                            _requests.Remove(request);

                        }

                        lastTimestamp = (long)(DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond);
                        retryElapsedMilis = (int)(lastTimestamp - retryBegginingTimestamp);
                    }
                }
            }

            _transactionTimeout = 20000 + retryElapsedMilis + elapsedMilis;
            _lastingClients = (ArrayList)_reservation.InvitedClients.Clone();

            return true;
        }

        private void DisseminateTentativelyBooked()
        {
            ReservationSlot actualSlot = (ReservationSlot)_reservation.ReservationSlots[_tentativelySlotIndex];

            actualSlot.SlotState = ReservationSlotState.TENTATIVELY_BOOKED;

            Type typExternal = typeof(CommonTypes.Client.IClientToClient);
            MethodInfo methodInf = typExternal.GetMethod("NotifyTentativelyBooked");

            object[] args = new object[] { _clientData.ClientId, _reservation.TicketNumber, actualSlot.SlotNumber, _lastingClients, _reservation };
            MessageDissemination.disseminateMessage(methodInf, _clientData, _lastingClients, args, true);
            _reservationThreadState = ReservationInitiatorState.WAITING_TB_ANSWERS;
        }

        private bool WaitTentativelyBookedAnswer()
        {
            long lastTimestamp, begginingTimeStampMilis = (long)(DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond);
            int elapsedMilis = 0;
            ReservationSlot currentTBSlot = _reservation.GetReservationSlot(_tentativelySlotIndex);

            //Wait for answers from dissemination
            while (_lastingClients.Count > 0 && _reservationThreadState == ReservationInitiatorState.WAITING_TB_ANSWERS
                && elapsedMilis < _reservationAnswerTimeout)
            {
                lock (_lockingObject)
                {
                    Monitor.Wait(_lockingObject, _reservationAnswerTimeout - elapsedMilis);

                    while (_requests.Count > 0)
                    {
                        Request request = _requests[0];

                        if (request.RequestProtocolPhase == RequestPhase.TENTATIVELY_BOOKED)
                        {
                            bool tentativelyBooked = request.Answer;
                            long slotNumber = request.SlotNumber;

                            if (slotNumber != currentTBSlot.SlotNumber)
                            {
                                _requests.RemoveAt(0);
                                continue;
                            }
                            else if (!tentativelyBooked)
                            {
                                return false;
                            }
                            _lastingClients.Remove(request.ClientId);
                        }
                        _requests.Remove(request);
                    }
                }

                lastTimestamp = (long)(DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond);
                elapsedMilis = (int)(lastTimestamp - begginingTimeStampMilis);
            }

            //Answer from some clients not received. Contact directly.
            int retryElapsedMilis = 0;

            while (_lastingClients.Count > 0)
            {
                long retryBegginingTimestamp = (long)(DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond);
                retryElapsedMilis = 0;

                Type typExternal = typeof(CommonTypes.Client.IClientToClient);
                MethodInfo methodInf = typExternal.GetMethod("NotifyTentativelyBooked");

                object[] args = new object[] { _clientData.ClientId, _reservation.TicketNumber, currentTBSlot.SlotNumber, _lastingClients, _reservation };
                MessageDissemination.disseminateMessage(methodInf, _clientData, _lastingClients, args, true);

                while (retryElapsedMilis < _reservationAnswerTimeout)
                {
                    lock (_lockingObject)
                    {
                        Monitor.Wait(_lockingObject, _reservationAnswerTimeout - retryElapsedMilis);

                        while (_requests.Count > 0)
                        {
                            Request request = _requests[0];

                            if (request.RequestProtocolPhase == RequestPhase.TENTATIVELY_BOOKED)
                            {
                                bool tentativelyBooked = request.Answer;
                                long slotNumber = request.SlotNumber;

                                if (slotNumber != currentTBSlot.SlotNumber)
                                {
                                    _requests.RemoveAt(0);
                                    continue;
                                }
                                else if (!tentativelyBooked)
                                {
                                    return false;
                                }
                                _lastingClients.Remove(request.ClientId);
                            }
                            _requests.Remove(request);
                        }
                    }
                    lastTimestamp = (long)(DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond);
                    retryElapsedMilis = (int)(lastTimestamp - retryBegginingTimestamp);
                }
            }

            _transactionTimeout = 20000 + retryElapsedMilis + elapsedMilis;
            _lastingClients = (ArrayList)_reservation.InvitedClients.Clone();
            return true;
        }

        private void DisseminateVoteCommitRequest()
        {
            ReservationSlot actualSlot = (ReservationSlot)_reservation.ReservationSlots[_tentativelySlotIndex];

            Type typExternal = typeof(CommonTypes.Client.IClientToClient);
            MethodInfo methodInf = typExternal.GetMethod("VoteCommitRequest");

            long transactionTimeout = _transactionTimeout + (long)(1.3 * _transactionTimeout);
            object[] args = new object[] { _clientData.ClientId, _reservation.TicketNumber, actualSlot.SlotNumber, transactionTimeout, _lastingClients };
            MessageDissemination.disseminateMessage(methodInf, _clientData, _lastingClients, args, true);
            _reservationThreadState = ReservationInitiatorState.WAITING_CR_ANSWERS;
        }

        private bool WaitCommitRequestAnswer()
        {
            long lastTimestamp, begginingTimeStampMilis = (long)(DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond);
            int elapsedMilis = 0;

            while (_lastingClients.Count > 0 && elapsedMilis < _transactionTimeout)
            {
                lock (_lockingObject)
                {
                    Monitor.Wait(_lockingObject, _transactionTimeout - elapsedMilis);

                    while (_requests.Count > 0)
                    {
                        Request request = _requests[0];

                        if (request.RequestProtocolPhase == RequestPhase.REQUEST_COMMIT && _lastingClients.Contains(request.ClientId))
                        {
                            if (!request.Answer)
                            {
                                return false;
                            }
                            _lastingClients.Remove(request.ClientId);
                        }
                        _requests.RemoveAt(0);
                    }
                }

                lastTimestamp = (long)(DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond);
                elapsedMilis = (int)(lastTimestamp - begginingTimeStampMilis);
            }

            if (_lastingClients.Count == 0)
            {
                _lastingClients = (ArrayList)_reservation.InvitedClients.Clone();
                return true;
            }
            else
            {
                return false;
            }
        }

        private void DisseminateBeginReservation()
        {
            Type typExternal = typeof(CommonTypes.Client.IClientToClient);
            MethodInfo methodInf = typExternal.GetMethod("BeginReservation");
            object[] args = new object[] { _reservation, new ArrayList(), _reservation.InvitedClients };
            MessageDissemination.disseminateMessage(methodInf, _clientData, _reservation.InvitedClients, args, true);
        }

        private void DisseminateTransactionAbort()
        {
            ReservationSlot actualSlot = (ReservationSlot)_reservation.ReservationSlots[_tentativelySlotIndex];
            _lastingClients = (ArrayList)_reservation.InvitedClients.Clone();

            Type typExternal = typeof(CommonTypes.Client.IClientToClient);
            MethodInfo methodInf = typExternal.GetMethod("AbortTransaction");

            object[] args = new object[] { _clientData.ClientId, _reservation.TicketNumber, actualSlot.SlotNumber, _lastingClients };
            MessageDissemination.disseminateMessage(methodInf, _clientData, _lastingClients, args, true);
        }

        private void DisseminateVoteDoCommitRequest()
        {
            ReservationSlot actualSlot = (ReservationSlot)_reservation.ReservationSlots[_tentativelySlotIndex];

            Type typExternal = typeof(CommonTypes.Client.IClientToClient);
            MethodInfo methodInf = typExternal.GetMethod("VoteDoCommit");

            object[] args = new object[] { _clientData.ClientId, _reservation.TicketNumber, actualSlot.SlotNumber, _lastingClients };
            MessageDissemination.disseminateMessage(methodInf, _clientData, _lastingClients, args, true);
            _reservationThreadState = ReservationInitiatorState.WAITING_DC_ANSWERS;
        }

        private bool WaitDoCommitRequestAnswer()
        {
            long lastTimestamp, begginingTimeStampMilis = (long)(DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond);
            int elapsedMilis = 0;

            while (_lastingClients.Count > 0 && elapsedMilis < _transactionTimeout)
            {
                lock (_lockingObject)
                {
                    Monitor.Wait(_lockingObject, _transactionTimeout - elapsedMilis);

                    while (_requests.Count > 0)
                    {
                        Request request = _requests[0];

                        if (request.RequestProtocolPhase == RequestPhase.COMMIT && _lastingClients.Contains(request.ClientId))
                        {
                            if (!request.Answer)
                            {
                                return false;
                            }
                            _lastingClients.Remove(request.ClientId);
                        }
                        _requests.RemoveAt(0);
                    }
                }
                lastTimestamp = (long)(DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond);
                elapsedMilis = (int)(lastTimestamp - begginingTimeStampMilis);
            }

            if (_lastingClients.Count == 0)
            {
                _lastingClients = (ArrayList)_reservation.InvitedClients.Clone();
                return true;
            }
            else
            {
                return false;
            }
        }
    }
}